package com.nekonomics
package domain.service

import config.AliPayConfigProvider
import domain.auxiliary.{BizContent, PayCallback, SimpleBizContent}
import domain.entity.Payment
import domain.service.util.TokenService
import error.OrderError
import persistence.repository.{OrderRepo, PaymentRepo}
import types.*
import types.AlipayTradeStatus.*
import types.OrderStatus.Cancelled
import utils.DoobieUtil.*

import com.alipay.api.AlipayClient
import com.alipay.api.internal.util.AlipaySignature
import com.alipay.api.request.{AlipayTradeCloseRequest, AlipayTradePagePayRequest, AlipayTradeQueryRequest}
import doobie.hikari.HikariTransactor
import zio.*
import zio.json.*

import java.time.OffsetDateTime
import scala.jdk.CollectionConverters.*

trait PaymentService {

  /** 创建支付单，返回支付链接（支付宝沙箱用的是跳转链接） */
  def createPayment(orderId: Long, totalAmount: Money, subject: String): DomainIO[String]

  /** 查询支付状态（主动轮询，一般用于保险起见） */
  def queryPayment(orderId: Long): DomainIO[Payment]

  /** 支付宝异步通知回调处理 */
  def handleAlipayCallback(params: Map[String, String]): DomainIO[Unit]


  /** 通知远端支付宝关闭交易，同时关闭本地的订单和支付交易 */
  def closeOrderAndPayment(orderId: Long): DomainIO[Unit]


  /** 用于定时关闭超时未支付订单 */
  def closeExpiredPaymentAndOrders(): DomainIO[Unit]


  /** 发起退款 */
  //  def refund(orderId: Long, refundAmount: Money, reason: Option[String]): DomainIO[Unit]

  /** 查询支付流水（比如查看支付历史，给管理后台用） */
  def getPaymentRecord(orderId: Long): DomainIO[Option[Payment]]
}


object PaymentService {
  val live: TaskLayer[PaymentService] =
    (config.layers ++ TokenService.live ++ persistence.layers ++ thirdparty.layers)
      >>> ZLayer.fromFunction(PaymentServiceLive.apply)
}


case class PaymentServiceLive(
  xa: HikariTransactor[Task],
  client: AlipayClient,
  config: AliPayConfigProvider,
  paymentRepo: PaymentRepo,
  orderRepo: OrderRepo
) extends PaymentService {


  def queryRemoteAlipayStatus(orderId: Long): DomainIO[AlipayTradeStatus] = {
    for {
      result <- ZIO.attempt {
        val request = new AlipayTradeQueryRequest()
        request.setBizContent(SimpleBizContent(orderId.toString).toJson)
        client.execute(request)
      }
      status <- {
        if result.isSuccess then
          ZIO.fromEither(
            AlipayTradeStatus.fromString(result.getTradeStatus)
          )
        else if result.getSubCode == "ACQ.TRADE_NOT_EXIST" then
          ZIO.succeed(NOT_CREATED)
        else ZIO.fail(new RuntimeException(s"支付宝返回未知状态: ${result.getSubCode} ${result.getSubMsg}"))
      }
    } yield status
  }


  /** 创建支付单，返回支付宝的html表单 */
  override def createPayment(orderId: Long, totalAmount: Money, subject: String): DomainIO[String] = {
    for {
      html <- ZIO.attempt {
        val request = new AlipayTradePagePayRequest()
        request.setNotifyUrl(config.notifyUrl)
        request.setBizContent(BizContent(orderId.toString, totalAmount, subject, timeout_express = config.timeoutExpress).toJson)
        client.pageExecute(request).getBody
      }
      _ <- paymentRepo.insert(orderId, totalAmount, PaymentStatus.Waiting, html).convertWithXa(xa)
      _ <- ZIO.logInfo(s"html\n$html")

    } yield html
  }

  /** 查询支付状态（主动轮询，一般用于保险起见） */
  override def queryPayment(orderId: Long): DomainIO[Payment] = {
    paymentRepo
      .findByOrderId(orderId)
      .convertWithXa(xa)
      .someOrFail(OrderError.OrderNotFound)
  }


  /** 支付宝异步通知回调处理 */
  override def handleAlipayCallback(params: Map[String, String]): DomainIO[Unit] = {
    val tx = (callback: PayCallback, success: Boolean) => for {
      _ <- if success then
        paymentRepo.markAsPaid(callback.orderId, Option(callback.tradeNo), callback.paidAt)
      else
        paymentRepo.markAsClosed(callback.orderId)
      _ <- if success then
        orderRepo.updateStatusById(callback.orderId, OrderStatus.Paid)
      else
        orderRepo.updateStatusById(callback.orderId, Cancelled)
    } yield ()

    for {
      isValid <- ZIO.attempt {
        AlipaySignature.rsaCheckV1(
          new java.util.HashMap(params.asJava),
          config.alipayPublicKey,
          "utf-8",
          "RSA2"
        )
      }
      _ <- ZIO.fail(new RuntimeException("支付回调验签失败")) when !isValid
      trade_status <- ZIO.fromOption(params.get("trade_status")).mapError(_ => new RuntimeException("支付包响应缺失trade_status"))
      status <- ZIO.fromEither(AlipayTradeStatus.fromString(trade_status))
      _ <- ZIO.logInfo(s"支付宝回调状态：$trade_status")
      _ <- status match {
        case TRADE_SUCCESS =>
          for {
            callback <- PayCallback.of(params)
            _ <- tx(callback, true).convertWithXa(xa) unlessZIO assertStatus(callback.orderId, OrderStatus.Paid)
          } yield ()

        case TRADE_CLOSED =>
          for {
            callback <- PayCallback.of(params)
            _ <- tx(callback, false).convertWithXa(xa) unlessZIO assertStatus(callback.orderId, OrderStatus.Cancelled)
          } yield ()

        // 暂不处理支付宝关闭退款的状态
        case TRADE_FINISHED | _ => ZIO.unit
      }
    } yield ()
  }


  private def assertStatus(orderId: Long, hope: OrderStatus) = {
    paymentRepo
      .findByOrderId(orderId)
      .convertWithXa(xa)
      .someOrFail(OrderError.OrderNotFound)
      .map(_.status == hope)
  }


  /** 查询支付流水（比如查看支付历史，给管理后台用） */
  override def getPaymentRecord(orderId: Long): DomainIO[Option[Payment]] = {
    paymentRepo.findByOrderId(orderId).convertWithXa(xa)
  }


  /** 通知远端支付宝关闭交易，同时关闭本地的订单和支付交易 */
  override def closeOrderAndPayment(orderId: Long): DomainIO[Unit] = {

    val tx = for {
      _ <- paymentRepo.markAsClosed(orderId)
      _ <- orderRepo.updateStatusById(orderId, OrderStatus.Cancelled)
    } yield ()

      for {
        response <- ZIO.attempt {
          val request = new AlipayTradeCloseRequest()
          request.setBizContent(SimpleBizContent(orderId.toString).toJson)
          client.execute(request)
        }
        _ <- {
          // 调用成功，说明本地状态早于支付宝的状态，直接处理
          if response.isSuccess then
            tx.convertWithXa(xa)
          // 调用失败，说明本地状态迟于支付宝的状态，只有等待买家付款状态下才能发起交易关闭，查询订单状态并处理
          else queryRemoteAlipayStatus(orderId).flatMap {
            case TRADE_CLOSED | NOT_CREATED =>
              paymentRepo.markAsClosed(orderId)
                .flatMap(_ => orderRepo.updateStatusById(orderId, OrderStatus.Cancelled))
                .convertWithXa(xa)
            case TRADE_SUCCESS | TRADE_FINISHED =>
              paymentRepo
                .markAsPaid(orderId, Option(response.getTradeNo), OffsetDateTime.now)
                .flatMap(_ => orderRepo.updateStatusById(orderId, OrderStatus.Paid))
                .convertWithXa(xa)
                .logError(s"Failed to update order status for order $orderId, tradeNo: ${response.getTradeNo}")
            case _ => ZIO.unit
          }
        }
      }
      yield ()

  }


  /** 用于定时关闭超时未支付订单 */
  override def closeExpiredPaymentAndOrders(): DomainIO[Unit] = {
    for {
      expired <- orderRepo.listByUnpaid(config.orderDurationMinutes).convertWithXa(xa)
      _ <- ZIO.foreachDiscard(expired)(order => closeOrderAndPayment(order.id) *> ZIO.logInfo(s"订单号：${order.id} 已被关闭"))
    }
    yield ()
  }

}
